﻿/*
Copyright (C) 2011 by Seemant Rajvanshi

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/

//********************************************************************************************
// Helper functions
//********************************************************************************************
Array.prototype.contains = function (obj) {
    var i = this.length;
    while (i--) {
        if (this[i] === obj) {
            return true;
        }
    }
    return false;
}
//********************************************************************************************
// Fluent fetch implementation
//********************************************************************************************
var fluentFetchXml = function () {
    this.varOutput = '';
    this.varEntityName = '';
    this.varCount = 0;
    this.varAllAttributes = false;
    this.varAttributes = [];
    this.varOrderAttributes = [];
    this.varFilters = [];
    this.varConditions = [];
    this.varError = false;

    this.count = function (totalCount) {
        /// <summary>Sets the total number of records to be retrieved.</summary>
        /// <param name="totalCount">Number of records to retrieve</param>
        /// <returns>this Object</returns>

        // Terminate in case of any errors
        if (this.varError) {
            return;
        }

        if (this.varCount != 0) {
            alert('Fetch Xml count property can only be set once.');
            this.varError = true;
            return;
        }

        this.varCount = totalCount;
        return this;
    }


    this.entityName = function (entityName) {
        /// <summary>Sets the name of the entity to be retrieved.</summary>
        /// <param name="entityName">Name of the entity to be reterived.</param>
        /// <returns>this Object</returns>

        // Terminate in case of any errors
        if (this.varError) {
            return;
        }

        if (this.varEntityName != '') {
            alert('Fetch Xml entity name property can only be set once.');
            this.varError = true;
            return;
        }

        this.varEntityName = entityName;
        return this;
    }


    this.attribute = function (attributeName) {
        /// <summary>Adds an attribute to the the query.</summary>
        /// <param name="attributeName">Attribute Name</param>
        /// <returns>this Object</returns>

        // Terminate in case of any errors
        if (this.varError) {
            return;
        }

        // Check if all attributes property is checked
        if (this.varAllAttributes) {
            alert('Cannot use attribute property when All attribute propery is used.');
            this.varError = true;
            return;
        }

        // Check for duplication
        if (this.varAttributes.contains(attributeName)) {
            alert('Duplicate attributes in the query.');
            this.varError = true;
            return;
        }

        this.varAttributes.push(attributeName);
        return this;
    }


    this.order = function (attributeName, descending) {
        /// <summary>Adds an attribute to the the query.</summary>
        /// <param name="attributeName">Attribute Name</param>
        /// <param name="descending">Descending Order</param>
        /// <returns>this Object</returns>

        // Terminate in case of any errors
        if (this.varError) {
            return;
        }

        // Check for duplication
        if (this.varOrderAttributes.contains(attributeName)) {
            alert('Duplicate order attributes in the query.');
            this.varError = true;
            return;
        }

        this.varOrderAttributes.push({ "attribute": attributeName, "descending": descending });
        return this;
    }


    this.filter = function (filterType) {
        /// <summary>Adds a filter to the the query.</summary>
        /// <param name="filterType">Filter tpe (and/or)</param>
        /// <returns>this Object</returns>

        // Terminate in case of any errors
        if (this.varError) {
            return;
        }

        this.varFilters.push(filterType);
        return this;
    }


    this.condition = function (attributeName, operator, value) {
        /// <summary>Adds a conditon to the query.</summary>
        /// <param name="attributeName">Attribute Name</param>
        /// <param name="operator">Operator</param>
        /// <param name="value">Value</param>
        /// <returns>this Object</returns>

        // Terminate in case of any errors
        if (this.varError) {
            return;
        }

        // Check if we have at least one filter type defined. If not then push one and associate this condition with
        // that filter type
        if (this.varFilters.length == 0) {
            // And is the default filter
            this.varFilters.push('and');
        }

        // We associate the current condition with the last entered filter
        this.varConditions.push({
            "attribute": attributeName,
            "operator": operator,
            "value": value,
            "filterId": this.varFilters.length - 1
        });
        return this;
    }


    this.getFetchXml = function () {
        /// <summary>Returns Fetch XML string.</summary>
        /// <returns>string</returns>

        // Terminate in case of any errors
        if (this.varError) {
            return;
        }

        if (this.varEntityName == '') {
            alert('Enity name is not defined.');
            this.varError = true;
            return;
        }

        // No attributes are defined
        if (this.varAttributes.length == 0 && this.varAllAttributes == false) {
            alert('No attributes are defined for the query.');
            this.varError = true;
            return;
        }

        // No conditions defined
        if (this.varFilters.length > 0 && this.varConditions == 0) {
            alert('No conditions are defined for the filter.');
            this.varError = true;
            return;
        }

        this.varOutput += '<fetch mapping="logical" count="';
        this.varOutput += this.varCount + '" version="1.0">';
        this.varOutput += '<entity name="' + this.varEntityName + '">';

        // Add all attributes to the query
        if (this.varAllAttributes == true) {
            this.varOutput += '<all-attributes />';
        } else {
            for (var i = 0; i < this.varAttributes.length; i++) {
                this.varOutput += '<attribute name="' + this.varAttributes[i] + '" />';
            }
        }


        // Order attributes
        for (var j = 0; j < this.varOrderAttributes.length; j++) {
            this.varOutput += '<order attribute="' + this.varOrderAttributes[j].attribute + '"';
            this.varOutput += ' descending="' + this.varOrderAttributes[j].descending + '" />';
        }

        // Add filters
        for (i = 0; i < this.varFilters.length; i++) {
            this.varOutput += '<filter type="' + this.varFilters[i] + '">';

            // Add conditions
            for (j = 0; j < this.varConditions.length; j++) {
                if (this.varConditions[j].filterId == i) {
                    this.varOutput += '<condition attribute="' + this.varConditions[j].attribute + '"';
                    this.varOutput += ' operator="' + this.varConditions[j].operator + '"';
                    this.varOutput += ' value="' + this.varConditions[j].value + '" />';
                }
            }
        }

        // Add filter end tags
        for (i = 0; i < this.varFilters.length; i++) {
            this.varOutput += '</filter>';
        }

        this.varOutput += '</entity></fetch>';

        return this.varOutput;
    }


    if (this instanceof fluentFetchXml) {
        return this.fluentFetchXml;
    }
    else {
        return new fluentFetchXml();
    }

}